<?php

require_once("Interfaces/AIS_JsonRpc2SMD.php");
require_once dirname(__FILE__).'/../DocParsing/AIS_DocComment.php';
require_once dirname(__FILE__).'/../DocParsing/AIS_DocCommentFunction.php';
class AIS_JsonRpc2SMDResponseParameter
{
	/**
	 * Type
	 *
	 * @var mixed
	 */
	public $type;
	/**
	 * Name
	 *
	 * @var string
	 */
	public $name;
	/**
	 * An optional description
	 *
	 * @var string
	 */
	public $description;
}
/**
 * The JsonRpc-Smd Response Service
 *
 */
class AIS_JSONRpc2ResponseService
{
	/**
	 * The name of the service
	 *
	 * @var string
	 */
	public $name;
	/**
	 * Parameters
	 *
	 * @var array{AIS_JsonRpc2SMDResponseParameter}
	 */
	public $parameters = array();
	/**
	 * Return Value
	 *
	 * @var mixed
	 */
	public $returns;
	/**
	 * An optional description
	 *
	 * @var unknown_type
	 */
	public $description;
}

/**
 * The JSONRpc-SMD Response
 *
 */
class AIS_JsonRpc2SMDResponse
{
	/**
	 * A name for this set of services
	 *
	 * @var string
	 */
	public $name;
	/**
	 * Version
	 *
	 * @var string
	 */
	public $SMDVersion = "2.0";
	/**
	 * The Services
	 *
	 * @var array{AIS_JSONRpc2ResponseService}
	 */
	public $services = array();
	/**
	 * The types contained in this SMD
	 *
	 * @var mixed[]
	 */
	public $types = array();
	/**
	 * An optional Description
	 *
	 * @var string
	 */
	public $description;
}
/**
 * A type description for classes in Json Schema as described in http://www.json.com/json-schema-proposal/
 * 
 *
 */
class AIS_JsonSchemaObjectTypeDescription
{
	/**
	 * A description for this type
	 * 
	 * @var string
	 */
	public $description;
	/**
	 * The name of the type
	 * 
	 * @var string
	 */
	public $id;
	/**
	 * The properties of the type
	 *
	 * @var mixed
	 */
	public $properties;
}
/**
 * Returns the SMD for all methods in a class. Experimental, may change.
 *
 */
class AIS_JsonRpc2SMDImpl implements AIS_JsonRpc2SMD 
{
	/**
	 * Sets the class that contains the service methods
	 *
	 * @param string $class the classname
	 */
	public function setClass($class)
	{
		$this->reflectionClass = new ReflectionClass($class);
		
	}
	/**
	 * Returns the SMD for the set class
	 *
	 * @return AIS_JsonRpc2SMDResponse
	 */
	public function getSMD()
	{
		$this->types = array();
		$resp = new AIS_JsonRpc2SMDResponse();
		$meth = $this->reflectionClass->getMethods();;
		$resp->name = $this->reflectionClass->getName();
		foreach($meth as $method)
		{
			/* @var $method ReflectionMethod */
			$method = $method;
			$service = new AIS_JSONRpc2ResponseService();
			$service->name = $method->getName();
			$param = $method->getParameters();
			$comment = new AIS_DocCommentFunction($method->getDocComment());
			$i = 0;
			foreach ($param as $parameter)
			{
				/* @var $parameter ReflectionParameter */
				$parameter = $parameter;
				$respParam = new AIS_JsonRpc2SMDResponseParameter();
				$respParam->name = $parameter->getName();
				$this->attachTypeSMD($comment->getParam('$'.$respParam->name),$respParam);
				array_push($service->parameters, $respParam);
				$i++;
			}
			$tag = $comment->getTag('@return');
			if ($tag)
			{
				$service->returns = $this->mapTypeToSMD($tag->getValue(0));
			}
			array_push($resp->services,$service);
		}
		$resp->types = $this->types;
		return json_encode($resp);
	}
	
	private $phpToJsonPrimitives = array(
		"mixed" => "any",
		"int" => "integer",
		"double" => "numeric",
		"float" => "numeric",
		"string" => "string",
		"bool" => "boolean"
	);

	/**
	 * Adds all object type descriptions this type contains and the type itself to the $this->types array
	 *
	 * @param string $type
	 */
	private function addJsonSchemaTypeDescription($type)
	{
		$refl = new ReflectionClass($type);
		$result = new AIS_JsonSchemaObjectTypeDescription();
		$comm = new AIS_DocComment($refl->getDocComment());
		$result->description = $comm->getComment();
		$result->id = $refl->getName();
		$this->types[$result->id] = $result;
		$attr = $refl->getProperties();
		$properties = new stdClass();
		foreach ($attr as $att)
		{
			/* @var $att ReflectionMethod */
			$comm = new AIS_DocComment($att->getDocComment());
			/* @var $tag AIS_DocCommentTagParamWithRestrictions */
			$tag = $comm->getTag('@var');
			if (!$tag)
			{
				throw new Exception('Attribute '.$att->getName().' must define type in class '.$result->id);
			}
			$obj = new stdClass();
			$type = $this->attachTypeSMD($tag, $obj);
			$obj->description = $comm->getComment();
			$name = $att->getName();
			$properties->$name = $obj;
		}
		$result->properties = $properties;
		
	}
	/**
	 * Checks if $this->type already has this class type
	 *
	 * @param string $type
	 * @return bool
	 */
	private function containsObjectType($type)
	{
		return isset($this->phpToJsonPrimitives[$type]) || AIS_DocComment::isArray($type) || isset($this->types[$type]);
	}
	
	/**
	 * Attaches the SMD type description to the object
	 *
	 * @param AIS_DocCommentTagParamWithRestrictions $tag The tag
	 * @param obj The object to attach the type at
	 */
	private function attachTypeSMD($tag, &$obj)
	{
		$type = $tag->getValue(0);
		$isObject = true;
		$smdType = $this->mapTypeToSMD($type, $isObject);
		if (!$isObject)
		{
			$this->attachRestrictions($tag, $obj);
		}
		$obj->type = $smdType;
	}
	/**
	 * Returns the SMD type of the given type
	 *
	 * @param string $type
	 * @param isObject defines if the type returned is an object or not
	 * @return string
	 */
	private function mapTypeToSMD($type, &$isObject)
	{
		if (isset($isObject))
		{
			$isObject = false;
		}
		$primitive = isset($this->phpToJsonPrimitives[$type])?$this->phpToJsonPrimitives[$type]:null;
		if ($primitive)
		{
			return $primitive;
		}
		if (AIS_DocComment::isArray($type))
		{
			$subType = $type;
			$arrType = null;
			$lastArray = null;
			do
			{
				$subType = AIS_DocComment::getArrayType($subType);
				if (!arrType)
				{
					$arrType = array();
				}
				else
				{
					$lastArray = array();
					array_push($arrType, $lastArray);
				}
			}while(AIS_DocComment::isArray($subType));
			if (!$this->containsObjectType($subType))
			{
				$this->addJsonSchemaTypeDescription($subType);
			}
			array_push($lastArray, $subType);
			return $arrType;
		}
		if (!$this->containsObjectType($type))
		{
			$this->addJsonSchemaTypeDescription($type);
		}
		if (isset($isObject))
		{
			$isObject = true;
		}
		return $type;
	}
	
	/**
	 * Attaches the restrictions to the type
	 *
	 * @param AIS_DocCommentTagParamWithRestrictions $tag
	 * @param mixed $obj
	 */
	private function attachRestrictions($tag, &$obj)
	{
		$restr = $tag->getRestrictions();
		foreach ($restr as $k => $v)
		{
			$obj->$k = $v;
		}
	}
	
	/**
	 * Holds the class that was set by $this->setClass
	 *
	 * @var ReflectionClass
	 */
	private $reflectionClass;
	/**
	 * Contains all the object types of the SMD
	 *
	 * @var mixed[]
	 */
	private $types = array();
}
?>